/* 
 * PROJECT: FLARManager
 * http://transmote.com/flar
 * Copyright 2009, Eric Socolofsky
 * --------------------------------------------------------------------------------
 * This work complements FLARToolkit, developed by Saqoosha as part of the Libspark project.
 *	http://www.libspark.org/wiki/saqoosha/FLARToolKit
 * FLARToolkit is Copyright (C)2008 Saqoosha,
 * and is ported from NYARToolkit, which is ported from ARToolkit.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this framework; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * For further information please contact:
 *	<eric(at)transmote.com>
 *	http://transmote.com/flar
 * 
 */

package com.transmote.flar.source {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	import flash.media.Camera;
	import flash.media.Video;

	/**
	 * use the contents of a Camera feed as a source image for FLARToolkit marker detection.
	 * 
	 * @author	Eric Socolofsky
	 * @url		http://transmote.com/flar
	 */
	public class FLARCameraSource extends Sprite implements IFLARSource {
		public static var MAX_CAMERA_WIDTH:Number = 640;
		public static var MAX_CAMERA_HEIGHT:Number = 480;
		
		private var sourceWidth:Number;
		private var sourceHeight:Number;
		private var _downsampleRatio:Number;
		private var _isMirrored:Boolean;
		
		private var camera:Camera;
		private var video:Video;
		private var videoContainer:Sprite;
		private var snapshotBitmap:Bitmap;
		private var snapshot:BitmapData;
		private var sampleMatrix:Matrix;
		
		/**
		 * constructor.
		 * @param	width				camera input display and marker tracking source image width.
		 * @param	height				camera input display and marker tracking source image height.
		 * @param	fps					framerate of camera capture.
		 * @param	downsampleRatio		amount to downsample camera input.
		 * 								adjust to balance between image quality and marker tracking performance.
		 * 								a value of 1.0 results in no downsampling;
		 * 								a value of 0.5 (the default) downsamples the camera input by half.
		 * 								downsampling is measured against FLARCameraSource.MAX_CAMERA_WIDTH / MAX_CAMERA_HEIGHT
		 * 								(default 640x480); change these values prior to instantiation to support
		 * 								cameras with a higher native resolution than 640x480.
		 * @param	isMirrored			set to true to flip the camera source horizontally,
		 * 								to make the screen appear as a mirror to the user.
		 * 								defaults to true.
		 * @throws	Error				if no camera is found.
		 * 								(thrown by this.init, called from this method.)
		 */
		public function FLARCameraSource (width:int=640, height:int=480, fps:Number=30, downsampleRatio:Number=0.5, isMirrored:Boolean=true) {
			this.init(width, height, fps, downsampleRatio, isMirrored);
		}
		
		/**
		 * update the BitmapData source.
		 */
		public function update () :void {
			this.snapshot.draw(this.video, this.sampleMatrix);
		}
		
		/**
		 * retrieve the BitmapData source used for analysis.
		 * NOTE: returns the actual BitmapData object, not a clone.
		 */
		public function get source () :BitmapData {
			return this.snapshot;
		}
		
		/**
		 * size of BitmapData source used for analysis.
		 */
		public function get sourceSize () :Rectangle {
			return new Rectangle(0, 0, this.sourceWidth, this.sourceHeight);
		}
		
		/**
		 * amount by which the BitmapData source is downsampled.
		 */
		public function get downsampleRatio () :Number {
			return this._downsampleRatio;
		}
		
		/**
		 * set to true to flip the camera image horizontally.
		 */
		public function get isMirrored () :Boolean {
			return this._isMirrored;
		}
		public function set isMirrored (val:Boolean) :void {
			this._isMirrored = val;
		}
		
		private function init (width:int, height:int, fps:Number, downsampleRatio:Number, isMirrored:Boolean) :void {
			this._downsampleRatio = downsampleRatio;
			this._isMirrored = isMirrored;
			
			// calculate source size given camera default width / height and downsampleRatio
			var rawSourceWidth:Number = MAX_CAMERA_WIDTH * this._downsampleRatio;
			var rawSourceHeight:Number = MAX_CAMERA_HEIGHT * this._downsampleRatio;
			
			var camNames:Array = Camera.names;
			
			// set up Camera and Video to display it
			var i:uint = Camera.names.length;
			while (i--) {
				// auto-select built-in USB camera (i.e. Mac ISight)
				if (Camera.names[i] == "USB Video Class Video") {
					break;
				}
			}
			
			// if no "USB Video Class Video" camera found, use default camera.
			if (i == uint.MAX_VALUE) {
				this.camera = Camera.getCamera();
			} else {
				this.camera = Camera.getCamera(i.toString());
			}			
			
			if (!this.camera) {
				// developers can include better feedback to the end user here if desired.
				throw new Error("Camera not found.  Please check your connections and ensure that your camera is not in use by another application.");
			}
			
			this.camera.setMode(rawSourceWidth, rawSourceHeight, fps);
			this.video = new Video(rawSourceWidth, rawSourceHeight);
			this.video.attachCamera(this.camera);
			
			// nest Video inside another sprite,
			// to ensure transformations appear in BitmapData sampling
			this.videoContainer = new Sprite();
			this.videoContainer.addChild(this.video);
			
			// scale and crop camera source to fit within specified width / height.
			var fitWidthRatio:Number = rawSourceWidth / width;
			var fitHeightRatio:Number = rawSourceHeight / height;
			
			if (fitHeightRatio < fitWidthRatio) {
				// fit to height, center horizontally, crop left/right edges
				this.sourceWidth = fitHeightRatio*width;
				this.sourceHeight = rawSourceHeight;
				this.video.x = -0.5 * (rawSourceWidth - this.sourceWidth);
			} else {
				// fit to width, center vertically, crop top/bottom edges
				this.sourceWidth = rawSourceWidth;
				this.sourceHeight = fitWidthRatio*height;
				this.video.y = 0.5 * (rawSourceHeight - this.sourceHeight);
			}
		
			// construct transformation matrix used when updating BitmapData source
			if (this._isMirrored) {
				this.sampleMatrix = new Matrix(-1, 0, 0, 1, this.sourceWidth-this.video.x, -this.video.y);
			} else {
				this.sampleMatrix = new Matrix(1, 0, 0, 1, -this.video.x, -this.video.y);
			}
			
			// set up BitmapData to use as FLARToolkit source,
			// and Bitmap to display it on-screen.
			this.snapshot = new BitmapData(this.sourceWidth, this.sourceHeight, false, 0);
			this.snapshotBitmap = new Bitmap(this.snapshot);
			this.snapshotBitmap.scaleX = this.snapshotBitmap.scaleY = width / this.sourceWidth;
			this.addChild(this.snapshotBitmap);
		}
	}
}